package com.zshy.base.core.contract;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.zshy.base.core.contract.enums.IEnum;
import com.zshy.base.core.utils.BizAssert;
import lombok.*;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

/**
 * 基础查询，PO依赖此类用于条件查询数据库
 * <p>
 * 注意子类不要添加@Accessors(chain = true)注解，否则会导致后面BeanKit#toMap(Object, Map, Collection) 获取不到属性
 *
 * @author zhouhengzhe
 */
@Data
@Slf4j
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public abstract class Query extends Page {
    /**
     * 对应数据库的>=语句，一般用于开始时间筛选，即：数据库字段 >= 入参
     */
    public static final String START_QUERY = "Start";
    /**
     * 对应数据库的<=语句，一般用于结束时间筛选，或定时任务的到期时间，即：数据库字段 <= 入参
     */
    public static final String END_QUERY = "End";
    /**
     * 对应数据库的!=语句 (XxxNot)
     */
    public static final String NOT_QUERY = "Not";
    /**
     * // 对应数据库的in语句，类型是集合或字符串（用英文逗号,分隔）
     */
    public static final String IN_QUERY = "In";
    /**
     * // 对应数据库的not in语句，类型是集合或字符串（用英文逗号,分隔）
     */
    public static final String NOT_IN_QUERY = "NotIn";
    /**
     * 对应数据库的like '%xxx%'语句
     */
    public static final String LIKE_QUERY = "Like";
    /**
     * 对应数据库的like 'xxx%'语句
     */
    public static final String LIKE_LEFT_QUERY = "LikeLeft";
    /**
     * 对应数据库的like '%xxx'语句
     */
    public static final String LIKE_RIGHT_QUERY = "LikeRight";
    /**
     * 对应数据库的not like '%xxx%'语句
     */
    public static final String NOT_LIKE_QUERY = "NotLike";

    /**
     * 对应数据库的>语句，一般用于数值区间最小值筛选
     */
    public static final String MIN_QUERY = "Min";
    /**
     * 对应数据库的<语句，一般用于数值区间最大值筛选
     */
    public static final String MAX_QUERY = "Max";
    /**
     * 对应数据库的>=语句，一般用于数值区间最小值筛选
     */
    public static final String MIN_EQUALS_QUERY = "MinEq";
    /**
     * 对应数据库的<=语句，一般用于数值区间最大值筛选
     */
    public static final String MAX_EQUALS_QUERY = "MaxEq";
    /**
     * 对应数据库的is null或is not null语句。true: xxx is null, false: xxx is not null
     */
    public static final String NULL_QUERY = "IsNull";
    /**
     * 关键字查询（or查询）,对应数据库的 a = xx or b=yy
     */
    public static final String ORS_QUERY = "ors";
    /**
     * 不参与查询的字段
     */
    public static final List<String> EXCLUDE_FIELDS = Arrays.asList("select",
            "groupBy",
            "having",
            "orderBys",
            "fields",
            "keyword",
            "ignoreTenantId",
            "fills",
            "orderBy",
            "keywords",
            "ignoreCount");
    /**
     * select字段列表，多个以split（,）分隔
     */
    protected String select;
    /**
     * 分组字段列表
     */
    protected String groupBy;
    /**
     * having过滤条件
     */
    protected String having;
    /**
     * 排序字段列表，多个以split（,）分隔：aField_DESC,bField_ASC
     */

    protected String orderBys;

    /**
     * or查询
     */
    protected Map<String, Object> ors;
    /**
     * // 关键字字段列表，多个以split（,）分隔，配合keyword使用，最终转换成ors查询
     */
    protected String fields;

    /**
     * 关键字查询(like '%s%')，配合fields使用
     */
    protected Object keyword;
    /**
     * 忽略租户ID查询，默认不忽略
     */
    @ToString.Exclude
    private boolean ignoreTenantId = false;
    /**
     * 聚合参数集，多个以split（,）分隔
     */
    protected String fills;
    /**
     * 查询分隔符：select/keyword/orderBys/fields/fills/xxIn/xxNotIn，默认英文逗号,
     */
    @ToString.Exclude
    protected String split = ",";

    @Deprecated
    @ToString.Exclude
    protected String[] orderBy;

    @Deprecated
    @ToString.Exclude
    protected Map<String, Object> keywords;


    public <Q extends Query> Q select(String... columns) {
        if (columns != null) {
            this.setSelect(String.join(split, columns));
        }
        return (Q) this;
    }

    public <Q extends Query> Q groupBy(String groupBy) {
        this.setGroupBy(groupBy);
        return (Q) this;
    }

    public <Q extends Query> Q having(String having) {
        this.setHaving(having);
        return (Q) this;
    }

    public <Q extends Query> Q current(Long current) {
        this.setCurrent(current);
        return (Q) this;
    }

    public <Q extends Query> Q size(Long size) {
        this.setSize(size);
        return (Q) this;
    }

    public <Q extends Query> Q orderBy(String... orderBys) {
        if (orderBys != null) {
            this.orderBys = String.join(split, orderBys);
        }
        return (Q) this;
    }


    // or查询
    public <Q extends Query> Q ors(Object... ors) {
        if (fills != null) {
            if (ors.length % 2 != 0) {
                throw new IllegalArgumentException("ors.length must not be singular");
            }
            Map<String, Object> orsMap = new HashMap<>();
            for (int i = 0; i < ors.length - 1; i += 2) {
                orsMap.put((String) ors[i], ors[i + 1]);
            }
            this.setOrs(orsMap);
        }
        return (Q) this;
    }

    public <Q extends Query> Q keyword(String fields, Object keyword) {
        this.setFields(fields);
        this.setKeyword(keyword);
        return (Q) this;
    }

    /**
     * 忽略租户ID查询
     *
     * @param <Q>
     * @return
     */
    public <Q extends Query> Q ignoreTenantId() {
        this.ignoreTenantId = true;
        return (Q) this;
    }

    /**
     * 忽略分页查询
     *
     * @param <Q>
     * @return
     */
    public <Q extends Query> Q ignorePage() {
        this.setSize(-1L);
        return (Q) this;
    }

    /**
     * 聚合哪些数据
     *
     * @param fills
     * @return
     */
    public <Q extends Query> Q fills(String... fills) {
        if (fills != null) {
            if (this.fills != null) {
                this.setFills(this.fills + split + String.join(split, fills));
            } else {
                this.setFills(String.join(split, fills));
            }
        }
        return (Q) this;
    }

    /**
     * 是否聚合
     *
     * @param fill
     * @return
     */
    public Boolean fill(String fill) {
        if (this.fills == null || this.fills.isEmpty()) {
            return false;
        }
        List<String> fills = Arrays.asList(this.fills.split(split));
        return fills.contains(fill);
    }

    /**
     * 执行自定义SQL后再聚合。如果通过当前Query找不到仓库则忽略
     *
     * @param models
     */
    public void doFills(List<Model> models) {
        if (models != null && !models.isEmpty()) {
            try {
                repository().fill(this, models);
            } catch (IllegalArgumentException ignore) {
                log.error("doFills: repository not found", ignore);
            }
        }
    }

    @JsonIgnore
    public long getStartIndex() {
        return (getCurrent() - 1L) * getSize();
    }

    @JsonIgnore
    public long getEndIndex() {
        return this.getStartIndex() + getSize();
    }

    public <T extends BaseRepository<M, Q>, M extends Model, Q extends Query> T repository() {
        return BaseRepository.of(this.getClass());
    }

    public <MP> MP mapper() {
        return repository().getMapper();
    }


    public List<Map<String, Object>> maps() {
        return repository().maps(this);
    }


    public Map<String, Object> map() {
        List<Map<String, Object>> result = maps();
        if (result == null || result.isEmpty()) {
            return Collections.emptyMap();
        }
        return result.get(0);
    }

    public <M extends Model> M one() {
        return (M) repository().one(this);
    }

    public <M extends Model> M one(IEnum iEnum) {
        M one = one();
        BizAssert.assertNullWarn(one, iEnum);
        return one;
    }

    public <M extends Model> M first() {
        return (M) repository().first(this);
    }

    public <M extends Model> M first(IEnum iEnum) {
        M first = first();
        BizAssert.assertNullWarn(first, iEnum);
        return first;
    }

    public Long count() {
        return repository().count(this);
    }

    public boolean exist() {
        return repository().exist(this);
    }

    public void exist(IEnum iEnum) {
        BizAssert.assertFalseWarn(exist(), iEnum);
    }

    public boolean notExist() {
        return !this.exist();
    }

    public void notExist(IEnum iEnum) {
        BizAssert.assertFalseWarn(notExist(), iEnum);
    }

    public <M extends Model> Page<M> page() {
        return (Page<M>) repository().page(this);
    }

    public <M extends Model> Page<M> page(IEnum iEnum) {
        Page<M> page = page();
        BizAssert.assertEmptyWarn(page.getRecords(), iEnum);
        return page;
    }

    public <M extends Model> List<M> list() {
        return (List<M>) repository().list(this);
    }

    public <M extends Model> List<M> list(IEnum iEnum) {
        List<M> list = list();
        BizAssert.assertEmptyWarn(list, iEnum);
        return list;
    }

}